/* Sentinel - IRC Statistical and Operator Services
** pubstats.c - Handle public (channel) StatServ commands
**
** Copyright W. Campbell and others.  See README for more details
** Some code Copyright: Jonathan George, Kai Seidler, ircd-hybrid Team,
**                      IRCnet IRCD developers.
**
** $Id: pubstats.c,v 1.25 2004/03/30 21:51:42 wcampbel Exp $
*/

#include "stats.h"
#include "struct.h"
#include "proto.h"
#include "match.h"
#include "text.h"
#include "s_string.h"
#include "slog.h"

#ifdef PUBSTATS

static char errstr[BUFSIZE];

static int p_map(struct User *, int, char **);
static int p_version_map(struct User *, int, char **);
static int p_tld(struct User *, int, char **);
static int p_tld_map(struct User *, int, char **);
static int p_country(struct User *, int, char **);
static int p_daily(struct User *, int, char **);
static int p_netstats(struct User *, int, char **);
static int p_url(struct User *, int, char **);
static int p_help(struct User *, int, char **);
static int p_server(struct User *, int, char **);

static struct MsgTable pub_table[] =
  {
    { PUB_MAP, 0, 0, NULL, p_map },
    { PUB_VERSIONMAP, 0, 0, NULL, p_version_map },
    { PUB_TLD, 0, 0, NULL, p_tld },
    { PUB_TLDMAP, 0, 0, NULL, p_tld_map },
    { PUB_COUNTRY, 0, 0, NULL, p_country },
    { PUB_DAILY, 0, 0, NULL, p_daily },
    { PUB_NETSTATS, 0, 0, NULL, p_netstats },
    { PUB_URL, 0, 0, NULL, p_url },
    { PUB_HELP, 0, 0, NULL, p_help },
    { PUB_SERVER, 0, 0, NULL, p_server },
    { NULL, 0, 0, NULL, NULL }
  };

void pubstats(struct User *u, char *target, char *param)
{
  char *parv[MAXPARA];
  int parc = 0;
  int i;
  int status;

  /* Clear the parv */
  for (i = 0; i < MAXPARA; i++)
    parv[i] = NULL;

  /* Clear the error string */
  errstr[0] = '\0';

  if (param != NULL)
    parc = msgtoken(param, parv);

  netinfo.requests++;

  if (parc == 0)
    return ;

  for (i = 0; pub_table[i].handler; i++)
  {
    if (!strcasecmp(parv[0], pub_table[i].name))
    {
      status = pub_table[i].handler(u, parc, parv);
      if (status == 1)
        slog(DEFAULT_LOG, L_ERR, "Error with statserv pub command "
             "%s: %s", parv[0],
             (errstr) ? errstr : "No error message");
      /* Matched a value, done, then return */
      return ;
    }
  }

  return ;
}

/* All of these commands are subsets of the full StatServ commands, optimized
** for channel use, showing no hidden servers or IPs.
*/

/* In all of these functions, parv[0] contains the command, followed
** by the parameters
*/

static int p_map(struct User *u, int parc, char *parv[])
{
  struct Server *s;
  int i;
  dlink_node *dl;

  notice(settings.pub_chan, StatServ.nick,
         "Network Map [NAME] [USERS/MAX] [OPERS/MAX] [PERCENT]");
  for (i = 0; i < S_TABLE_SIZE; i++)
  {
    DLINK_FOREACH(dl, serverhash[i].head)
    {
      s = dl->data;

      if (!IsSHide(s))
      {
        if (IsPubDelayed())
          select_delay();
        notice(settings.pub_chan, StatServ.nick,
                "%-30s [%3d/%3d] [%2d/%2d] [%2.0f%%]",
                s->name, s->users, s->stats->maxusers, s->opers,
                s->stats->maxopers,
                (float)s->users / (float)netinfo.users * 100);
      }
    }
  }
  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick,
          "%-30s [%4d/%4d] [%3d/%3d] [100%%]",
          "Totals:", netinfo.users, netinfo.maxusers, netinfo.opers,
          netinfo.maxopers);

  return 0;
}

static int p_version_map(struct User *u, int parc, char *parv[])
{
#ifdef TRACK_VERSION
  struct Server *s = NULL;
  int i;
  dlink_node *dl;

  notice(settings.pub_chan, StatServ.nick,
          "[Server]                      [Version]");
  for (i = 0; i < S_TABLE_SIZE; i++)
  {
    DLINK_FOREACH(dl, serverhash[i].head)
    {
      s = dl->data;

      if (!IsSHide(s))
      {
        if (IsPubDelayed())
          select_delay();
        notice(settings.pub_chan, StatServ.nick, "%-30s %s", s->name,
                (s->version && s->version[0]) ?  s->version :
                "Unknown Version");
      }
    }
  }

  return 0;
#else

  notice(settings.pub_chan, StatServ.nick, "%s Disabled", PUB_VERSIONMAP);
  return 0;
#endif
}

static int p_tld(struct User *u, int parc, char *parv[])
{
  struct TLD *tmp;
  char *tld = parv[1];

  if (tld == NULL)
  {
    notice(settings.pub_chan, StatServ.nick, "Syntax: %s <tld>", PUB_TLD);
    return 0;
  }

  if (*tld == '*')
    tld++;
  if (*tld == '.')
    tld++;

  /* Catch it when it's just * or . */
  if (*tld == '\0')
  {
    notice(settings.pub_chan, StatServ.nick, "Syntax: %s <tld>", PUB_TLD);
    return 0;
  }

  tmp = findtld(tld);

  if (tmp == NULL)
  {
    notice(settings.pub_chan, StatServ.nick,
            "Top Level Domain %s does not exist.", tld);
    return 0;
  }

  notice(settings.pub_chan, StatServ.nick, "%s (%s)", tmp->country, tmp->tld);
  if (tmp->native != NULL)
    notice(settings.pub_chan, StatServ.nick, "Native Name: %s", tmp->native);

  return 0;
}

static int p_tld_map(struct User *u, int parc, char *parv[])
{
  struct TLD *t;
  int i;

  notice(settings.pub_chan, StatServ.nick, "   TLD Number / Percent  Country");
  notice(settings.pub_chan, StatServ.nick, "   --- ------   -------  -------");

  for (i = 0; i < T_TABLE_SIZE; i++)
  {
    for (t = tldlist[i]; t; t = t->next)
    {
      if (t->users != 0)
      {
        if (IsPubDelayed())
          select_delay();
        notice(settings.pub_chan, StatServ.nick, "%9s %3d (%2.0f%%) -> %s",
                t->tld, t->users, (float)t->users / (float)netinfo.users * 100,
                t->country);
      }
    }
  }

  return 0;
}

static int p_url(struct User *u, int parc, char *parv[])
{
  if (!IsHtml())
  {
    notice(settings.pub_chan, StatServ.nick, "HTML Dump Disabled");
    return 0;
  }

  notice(settings.pub_chan, StatServ.nick, "HTML Status URL: %s", settings.url);
  return 0;
}

static int p_help(struct User *u, int parc, char *parv[])
{
  notice(settings.pub_chan, StatServ.nick,
          "Supported commands: %s %s %s %s %s %s %s %s %s %s",
          PUB_MAP, PUB_VERSIONMAP, PUB_TLD, PUB_TLDMAP,
          PUB_COUNTRY, PUB_DAILY, PUB_NETSTATS, PUB_SERVER,
          PUB_URL, PUB_HELP);
  return 0;
}

static int p_country(struct User *u, int parc, char *parv[])
{
  int i;
  struct TLD *t;
  char search[LINE];
  char search2[LINE];
  int matches = 0;

  if (parv[1] == NULL)
  {
    notice(settings.pub_chan, StatServ.nick, "Syntax: %s <countryname>",
           parv[0]);
    return 0;
  }

  /* Wrap the entered search string with *'s */
  ExpandParv(search2, LINE, 1, parc, parv);
  snprintf(search, LINE, "*%s*", search2);

  for (i = 0; i < T_TABLE_SIZE; i++)
  {
    for (t = tldlist[i]; t; t = t->next)
    {
      if (matches == COUNTRY_LIMIT)
      {
        notice(settings.pub_chan, StatServ.nick,
                "Too many matches, only %d returned", matches);
        return 0;
      }
      if (match(search, t->country) == 0)
      {
        matches++;
        notice(settings.pub_chan, StatServ.nick, "Match %d:  %s (%s)", matches,
                t->country, t->tld);
        if (IsPubDelayed())
          select_delay();
        if (t->native != NULL)
        {
          notice(settings.pub_chan, StatServ.nick, "Native Name: %s",
                 t->native);
          if (IsPubDelayed())
            select_delay();
        }
        /* There can be multiple matches for the
        ** same strings, consider Korea or Congo
        */
      }
    }
  }

  return 0;
}

static int p_daily(struct User *u, int parc, char *parv[])
{
  notice(settings.pub_chan, StatServ.nick, "  Maximum Servers: %-2d %s",
          daily.servers, sftime(daily.t_servers));
  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick, "  Maximum Users: %-2d %s",
          daily.users, sftime(daily.t_users));
  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick, "  Maximum Opers: %-2d %s",
          daily.opers, sftime(daily.t_opers));

  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick, "  Maximum Channels: %-2d %s",
          daily.channels, sftime(daily.t_channels));
  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick, "  Connections Today: %ld",
         daily.connections);
  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick,
          "All daily statistics are reset at midnight local time.");
  return 0;
}

static int p_netstats(struct User *u, int parc, char *parv[])
{
  notice(settings.pub_chan, StatServ.nick, "   Current Users: %ld",
         netinfo.users);
  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick, "   Maximum Users: %-2ld %s",
          netinfo.maxusers, sftime(netinfo.t_maxusers));
  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick,
          "   Client Connections: %ld since %s", netinfo.connections,
          sftime(me.t_start));
  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick, "   Current Opers: %d",
         netinfo.opers);
  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick, "   Maximum Opers: %-2d %s",
          netinfo.maxopers, sftime(netinfo.t_maxopers));
  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick, "   Users Set Away: %-2d",
         netinfo.away);
  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick, "   Maximum Away: %-2d %s",
          netinfo.maxaway, sftime(netinfo.t_maxaway));
  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick, "   Users Set Invisible: %-2ld",
         netinfo.invis);
  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick, "   Current Servers: %d",
         netinfo.servers);
  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick, "   Maximum Servers: %-2d %s",
         netinfo.maxservers, sftime(netinfo.t_maxservers));

  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick, "   Current Channels: %d",
         netinfo.channels);
  if (IsPubDelayed())
    select_delay();
  notice(settings.pub_chan, StatServ.nick, "   Maximum Channels: %-2d %s",
          netinfo.maxchannels, sftime(netinfo.t_maxchannels));
  return 0;
}

static int p_server(struct User *u, int parc, char *parv[])
{
  struct Server *s = NULL;
  struct Stats *st;
  int found = 0;
  dlink_node *dl;

  if (parc < 2)
  {
    notice(settings.pub_chan, StatServ.nick, "Syntax: %s <servermask>",
           PUB_SERVER);
    return 0;
  }

  DLINK_FOREACH(dl, shead.head)
  {
    st = dl->data;
    if (match(parv[1], st->name) == 0)
    {
      s = findserver(st->name);
      if ((s != NULL) && IsSHide(s))
        continue;
      if (findhidden(parv[1]))
        continue;
      found++;
      notice(settings.pub_chan, StatServ.nick, "Statistics for %s since %s",
             st->name, sftime(st->starttime));
      if (IsPubDelayed())
        select_delay();
      if (s == NULL)
        notice(settings.pub_chan, StatServ.nick, "Server Last Seen:  %s",
               sftime(st->lastseen));
      if (IsPubDelayed())
        select_delay();
      if (s != NULL)
        notice(settings.pub_chan, StatServ.nick,
                "Current Users: %-3ld (%.0f%%) (%ld invisible)",
                s->users, (float)s->users /
                (float)netinfo.users * 100, s->invis);
      if (IsPubDelayed())
        select_delay();
      notice(settings.pub_chan, StatServ.nick, "Maximum Users: %-3ld %s",
              st->maxusers, sftime(st->t_maxusers));
      if (IsPubDelayed())
        select_delay();
      if (s != NULL)
        notice(settings.pub_chan, StatServ.nick, "Current Opers: %d", s->opers);
      if (IsPubDelayed())
        select_delay();
      notice(settings.pub_chan, StatServ.nick, "Maximum Opers: %-3d %s",
             st->maxopers, sftime(st->t_maxopers));

      if (s != NULL)
        notice(settings.pub_chan, StatServ.nick, "Current Away: %d", s->away);
      if (IsPubDelayed())
        select_delay();
      notice(settings.pub_chan, StatServ.nick, "Maximum Away: %-3d %s",
              st->maxaway, sftime(st->t_maxaway));
      if (IsPubDelayed())
        select_delay();
      notice(settings.pub_chan, StatServ.nick, "  IRCop Kills: %d",
             st->operkills);
      if (IsPubDelayed())
        select_delay();
      notice(settings.pub_chan, StatServ.nick, " Server Kills: %d",
             st->serverkills);
      if (IsPubDelayed())
        select_delay();
      if (st->numsplits > 0)
        notice(settings.pub_chan, StatServ.nick,
               "%s has split from the network %d time%s.", st->name,
                st->numsplits, (st->numsplits == 1) ? "" : "s");
      else
        notice(settings.pub_chan, StatServ.nick,
                "%s has never split from the network.", st->name);
      if (IsPubDelayed())
        select_delay();
#ifdef TRACK_VERSION

      if (s != NULL)
      {
        if (s->version[0] != '\0')
          notice(settings.pub_chan, StatServ.nick, "Version: %s", s->version);
        else
          notice(settings.pub_chan, StatServ.nick, "Version Unknown");
      }
#endif

    }
  }
  if (found == 0)
  {
    notice(settings.pub_chan, StatServ.nick, "No information available on %s",
           parv[1]);
  }
  else
  {
    notice(settings.pub_chan, StatServ.nick, "End of Statistics.");
  }

  return 0;
}

#endif
